﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

namespace Twodi.Structs
{
    /// <summary>
    /// Define a estrutura de uma linha.
    /// </summary>
    public struct Line : IEquatable<Line>
    {
        /// <summary>
        /// Ponto inicial da linha.
        /// </summary>
        public Vector2 Begin;

        /// <summary>
        /// Ponto final da linha.
        /// </summary>
        public Vector2 End;

        /// <summary>
        /// Construtor padrão.
        /// </summary>
        /// <param name="source">Ponto inicial da linha.</param>
        /// <param name="destination">Ponto final da linha.</param>
        public Line(Vector2 source, Vector2 destination)
        {
            this.Begin = source;
            this.End = destination;
        }

        /// <summary>
        /// Construtor padrão.
        /// </summary>
        /// <param name="x1">Ponto X do inicio da linha.</param>
        /// <param name="y1">Ponto Y do inicio da linha.</param>
        /// <param name="x2">Ponto X do fim da linha.</param>
        /// <param name="y2">Ponto Y do fim da linha.</param>
        public Line(float x1, float y1, float x2, float y2)
        {
            this.Begin.X = x1;
            this.Begin.Y = y1;
            this.End.X = x2;
            this.End.Y = y2;
        }
        
        /// <summary>
        /// Angulo da linha.
        /// </summary>
        public double Angle
        {
            get
            {
                return Math.Atan2(End.Y - Begin.Y, End.X - Begin.X);
            }
        }

        /// <summary>
        /// Distancia entre o ponto inicial e final da linha.
        /// </summary>
        public float Distance
        {
            get
            {
                return Vector2.Distance(Begin, End);
            }
        }

        /// <summary>
        /// Verificar equalidade com outra linha.
        /// </summary>
        /// <param name="other">Linha a ser verificada.</param>
        /// <returns></returns>
        public bool Equals(Line other)
        {
            return this.Begin == other.Begin && this.End == other.End;
        }

        /// <summary>
        /// Verificar se esta linha intercede com um ponto.
        /// </summary>
        /// <param name="other">Linha a ser verificada.</param>
        /// <returns></returns>
        public bool Intersect(Vector2 point)
        {
            return Vector2.Distance(Begin, point) + Vector2.Distance(point, End) == Vector2.Distance(Begin, End);
        }

        /// <summary>
        /// Verificar se esta linha intercede com um ponto.
        /// </summary>
        /// <param name="other">Linha a ser verificada.</param>
        /// <returns></returns>
        public bool Intersect(Point point)
        {
            return Vector2.Distance(Begin, new Vector2(point.X, point.Y)) + Vector2.Distance(new Vector2(point.X, point.Y), End) == Vector2.Distance(Begin, End);
        }

        /// <summary>
        /// Verificar se esta linha intercede com um circulo.
        /// </summary>
        /// <param name="other">circulo a ser verificado.</param>
        /// <returns></returns>
        public bool Intersect(Circle circle)
        {
            return Vector2.Distance(circle.Center, NearestPoint(circle.Center)) <= circle.Radius;
        }

        /// <summary>
        /// Verificar se esta linha intercede com outra linha.
        /// </summary>
        /// <param name="line">Linha para ser verificada.</param>
        /// <see cref="http://paulbourke.net/geometry/lineline2d/"/>
        /// <returns></returns>
        public bool Intersect(Line line)
        {
            // Denominator for ua and ub are the same, so store this calculation
            double d =
               (line.End.Y - line.Begin.Y) * (this.End.X - this.Begin.X)
               -
               (line.End.X - line.End.X) * (this.End.Y - this.Begin.Y);

            //n_a and n_b are calculated as seperate values for readability
            double n_a =
               (line.End.X - line.End.X) * (this.Begin.Y - line.Begin.Y)
               -
               (line.End.Y - line.Begin.Y) * (this.Begin.X - line.Begin.X);

            double n_b =
               (this.End.X - this.Begin.X) * (this.Begin.Y - line.Begin.Y)
               -
               (this.End.Y - this.Begin.Y) * (this.Begin.X - line.Begin.X);

            // Make sure there is not a division by zero - this also indicates that
            // the lines are parallel.  
            // If n_a and n_b were both equal to zero the lines would be on top of each 
            // other (coincidental).  This check is not done because it is not 
            // necessary for this implementation (the parallel check accounts for this).
            if (d == 0)
                return false;

            // Calculate the intermediate fractional point that the lines potentially intersect.
            double ua = n_a / d;
            double ub = n_b / d;

            // The fractional point will be between 0 and 1 inclusive if the lines
            // intersect.  If the fractional calculation is larger than 1 or smaller
            // than 0 the lines would need to be longer to intersect.
            if (ua >= 0d && ua <= 1d && ub >= 0d && ub <= 1d)
            {
                return true;
            }

            return false;
        }

        /// <summary>
        /// Verificar ponto mais proximo da linha ao ponto especificado.
        /// </summary>
        /// <param name="point">Ponto a ser usado como referencia.</param>
        /// <see cref="http://funplosion.com/devblog/collision-detection-line-vs-point-circle-and-rectangle.html"/>
        /// <returns></returns>
        public Vector2 NearestPoint(Vector2 point)
        {
            //get the normalized line segment vector
            Vector2 v = this.End - this.Begin;
            v.Normalize();

            //determine the point on the line segment nearest to the point p
            float distanceAlongLine = Vector2.Dot(point, v) - Vector2.Dot(this.Begin, v);
            Vector2 nearestPoint;
            if (distanceAlongLine < 0)
            {
                //closest point is A
                nearestPoint = this.Begin;
            }
            else if (distanceAlongLine > Vector2.Distance(this.Begin, this.End))
            {
                //closest point is B
                nearestPoint = this.End;
            }
            else
            {
                //closest point is between A and B... A + d  * ( ||B-A|| )
                nearestPoint = this.Begin + distanceAlongLine * v;
            }

            return nearestPoint;
        }
    }
}
